package com.codefish.framework.spring.beans.factory.xml;

import com.codefish.framework.spring.beans.exception.BeanDefinitionStoreException;
import com.codefish.framework.spring.beans.factory.support.*;
import com.codefish.framework.spring.context.annotation.ClassPathBeanDefinitionScanner;
import com.codefish.framework.spring.core.io.Resource;
import com.codefish.framework.spring.core.utils.AssertUtils;
import com.codefish.framework.spring.core.utils.ConvertUtils;
import com.codefish.framework.spring.core.utils.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;

/**
 * 从xml文件中加载BeanDefinition
 *
 * @author <a href='https://gitee.com/hzf2281'>codefish</a>
 * @version 1.0.0
 * @since 2022/10/01 下午 04:12
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public static final String BEAN_NODE = "bean";
    public static final String BEAN_NODE_ATTRIBUTE_ID = "id";
    public static final String BEAN_NODE_ATTRIBUTE_CLASS = "class";
    public static final String BEAN_NODE_ATTRIBUTE_SCOPE = "scope";
    public static final String BEAN_NODE_ATTRIBUTE_INIT_METHOD = "init-method";
    public static final String BEAN_NODE_ATTRIBUTE_DESTROY_METHOD = "destroy-method";
    public static final String BEAN_NODE_ATTRIBUTE_LAZY_INIT = "lazy-init";
    public static final String PROPERTY_NODE = "property";
    public static final String PROPERTY_NODE_ATTRIBUTE_NAME = "name";
    public static final String PROPERTY_NODE_ATTRIBUTE_VALUE = "value";
    public static final String PROPERTY_NODE_ATTRIBUTE_REF = "ref";
    public static final String CONTEXT_COMPONENT_SCAN = "context:component-scan";
    public static final String CONTEXT_COMPONENT_SCAN_ATTRIBUTE_BASE_PACKAGE = "base-package";

    public XmlBeanDefinitionReader(IBeanDefinitionRegistry beanDefinitionRegistry) {
        super(beanDefinitionRegistry);
    }

    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        AssertUtils.notNull(resource, "资源对象不能为null");
        //从xml中加载BeanDefinition
        Document root = null;
        try (InputStream is = resource.getInputStream()) {
            //解析xml文档
            root = doLoadDocument(is);
            AssertUtils.notNull(root, "解析后的root不能为空");
        } catch (Exception e) {
            throw new BeanDefinitionStoreException(resource.getResourcePath(), e);
        }

        int beanCount = 0;
        //获取所有<bean/>节点
        NodeList beanNodeList = root.getElementsByTagName(BEAN_NODE);
        for (int i = 0; i < beanNodeList.getLength(); i++) {
            Element beanNode = (Element) beanNodeList.item(i);
            String beanName = beanNode.getAttribute(BEAN_NODE_ATTRIBUTE_ID);
            BeanDefinition bd = parseBeanNode(beanName, beanNode, resource);
            //注册BeanDefinition
            getRegistry().registerBeanDefinition(beanName, bd);
            beanCount++;
        }
        //扫描配置的所有包路径下的组件，并注册到容器中
        beanCount += registerAnnotationBeans(root);
        return beanCount;
    }

    /**
     * 从输入流解析xml文档
     *
     * @param is 输入流
     * @return 解析结果
     * @throws ParserConfigurationException ParserConfigurationException
     * @throws IOException                  IOException
     * @throws SAXException                 SAXException
     */
    protected Document doLoadDocument(InputStream is) throws ParserConfigurationException, IOException, SAXException {
        //获取DocumentBuilderFactory实例
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        //获取builder
        DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
        //解析输入流。获取Document
        return documentBuilder.parse(is);
    }

    /**
     * 将xml的bean节点解析为BeanDefinition
     *
     * @param beanNode
     * @return
     * @throws BeanDefinitionStoreException
     */
    protected BeanDefinition parseBeanNode(String beanName, Element beanNode, Resource resource) throws BeanDefinitionStoreException {
        if (!"bean".equals(beanNode.getNodeName())) {
            throw new BeanDefinitionStoreException(resource.getResourcePath(), "节点标签必须是<" + BEAN_NODE + "/>");
        }
        String className = beanNode.getAttribute(BEAN_NODE_ATTRIBUTE_CLASS);
        BeanDefinition bd;

        if (StringUtils.isEmpty(className) || StringUtils.isEmpty(beanName)) {
            throw new BeanDefinitionStoreException(resource.getResourcePath(), beanName, "节点的" + BEAN_NODE_ATTRIBUTE_ID + "属性或" + BEAN_NODE_ATTRIBUTE_CLASS + "属性不能为空");
        }
        try {
            //设置类对象
            Class<?> clazz = getBeanClassLoader().loadClass(className);
            bd = new BeanDefinition(clazz);
        } catch (ClassNotFoundException e) {
            throw new BeanDefinitionStoreException(resource.getResourcePath(), beanName, e);
        }
        //解析scope属性
        String scope = beanNode.getAttribute(BEAN_NODE_ATTRIBUTE_SCOPE);
        if (!StringUtils.isEmpty(scope)) {
            bd.setScope(scope);
        }
        //解析init-method属性
        String customerInitMethodName = beanNode.getAttribute(BEAN_NODE_ATTRIBUTE_INIT_METHOD);
        if (!StringUtils.isEmpty(customerInitMethodName)) {
            bd.setInitMethod(customerInitMethodName);
        }
        //解析destroy-method属性
        String customerDestroyMethodName = beanNode.getAttribute(BEAN_NODE_ATTRIBUTE_DESTROY_METHOD);
        if (!StringUtils.isEmpty(customerDestroyMethodName)) {
            bd.setDestroyMethod(customerDestroyMethodName);
        }
        //解析lazy-init属性
        String lazyInit = beanNode.getAttribute(BEAN_NODE_ATTRIBUTE_LAZY_INIT);
        if (!StringUtils.isEmpty(lazyInit)) {
            bd.setLazyInit(ConvertUtils.convert(lazyInit, Boolean.class));
        }
        //解析<Property/>标签
        NodeList propertyNodes = beanNode.getElementsByTagName(PROPERTY_NODE);
        for (int i = 0; i < propertyNodes.getLength(); i++) {
            Element propertyNode = (Element) propertyNodes.item(i);
            String name = propertyNode.getAttribute(PROPERTY_NODE_ATTRIBUTE_NAME);
            String value = propertyNode.getAttribute(PROPERTY_NODE_ATTRIBUTE_VALUE);
            String ref = propertyNode.getAttribute(PROPERTY_NODE_ATTRIBUTE_REF);
            if (StringUtils.isEmpty(name)) {
                throw new BeanDefinitionStoreException(resource.getResourcePath(), beanName, PROPERTY_NODE + "节点的"
                        + PROPERTY_NODE_ATTRIBUTE_NAME + "不能为空");
            }
            if (!StringUtils.isEmpty(value)) {
                if (!StringUtils.isEmpty(ref)) {
                    throw new BeanDefinitionStoreException(resource.getResourcePath(), beanName, PROPERTY_NODE + "节点的"
                            + PROPERTY_NODE_ATTRIBUTE_VALUE + "和" + PROPERTY_NODE_ATTRIBUTE_REF + "不能同时存在");
                }
                bd.addPropertyValue(new PropertyValue(name, value));
            } else if (!StringUtils.isEmpty(ref)) {
                bd.addPropertyValue(new PropertyValue(name, new BeanReference(ref)));
            } else {
                throw new BeanDefinitionStoreException(resource.getResourcePath(), beanName, PROPERTY_NODE + "节点必须指定"
                        + PROPERTY_NODE_ATTRIBUTE_VALUE + "或" + PROPERTY_NODE_ATTRIBUTE_REF);
            }
        }
        return bd;
    }

    /**
     * 读取xml对于包路径的配置，扫描并注册包下所有的组件
     *
     * @param root xml文档对象
     * @return 注册的BeanDefinition数量
     */
    protected int registerAnnotationBeans(Document root) {

        NodeList nodeList = root.getElementsByTagName(CONTEXT_COMPONENT_SCAN);
        Set<String> packageToBeScan = new HashSet<>();
        //解析<context:component-scan/>节点
        for (int i = 0; i < nodeList.getLength(); i++) {
            Element node = (Element) nodeList.item(i);
            packageToBeScan.add(node.getAttribute(CONTEXT_COMPONENT_SCAN_ATTRIBUTE_BASE_PACKAGE));
        }
        //扫描注解bean
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry());
        return scanner.scanComponents(packageToBeScan);
    }

}
